package com.fx.jfxsupport;

import com.utils.FontUtil;
import com.utils.ImageUtil;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import com.fx.SystemTrayIcon;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;

//import com.fx.SystemTrayIcon;

/**
 * The Class AbstractJavaFxApplicationSupport.
 *
 * @author Felix Roske
 */

@EnableAutoConfiguration
@Slf4j(topic = "界面UI加载类")
public abstract class AbstractJavaFxApplicationSupport extends Application {

    /**
     * 调用系统任务栏托盘
     */
    public static SystemTrayIcon oldtrayIcon;
    /**
     * 调用弹出菜单
     */
    public static JPopupMenu popupMenu = new JPopupMenu();

    /**
     * 调用字体
     */
    private static Font derivedFont = FontUtil.getFont();
    private static int fontSize = derivedFont.getSize();
    private static int iconSize = derivedFont.getSize(); /*new Double(fontSize * 1.5).intValue();*/

//    private static log log = logFactory.getlog(AbstractJavaFxApplicationSupport.class);

    private static String[] savedArgs = new String[0];

    static Class<? extends AbstractFxmlView> savedInitialView;
    static SplashScreen splashScreen;
    private static ConfigurableApplicationContext applicationContext;


    private double xOffset = 0;
    private double yOffset = 0;

    private static List<Image> icons = new ArrayList<>();
    private final List<Image> defaultIcons = new ArrayList<>();

    private final CompletableFuture<Runnable> splashIsShowing;

    protected AbstractJavaFxApplicationSupport() {
        splashIsShowing = new CompletableFuture<>();
    }

    public static Stage getStage() {
        return GUIState.getStage();
    }

    public static Scene getScene() {
        return GUIState.getScene();
    }

    public static HostServices getAppHostServices() {
        return GUIState.getHostServices();
    }

//    public static SystemTray getSystemTray() {
//        return GUIState.getSystemTray();
//    }

    /**
     * @param window The FxmlView derived class that should be shown.
     * @param mode   See {@code javafx.stage.Modality}.
     */
    public static void showView(final Class<? extends AbstractFxmlView> window, final Modality mode) {
        final AbstractFxmlView view = applicationContext.getBean(window);
        Stage newStage = new Stage();

        Scene newScene;
        if (view.getView().getScene() != null) {
            // This view was already shown so
            // we have a scene for it and use this one.
            newScene = view.getView().getScene();
        } else {
            newScene = new Scene(view.getView());
        }

        newStage.setScene(newScene);
        newStage.initModality(mode);
        newStage.initOwner(getStage());
        newStage.setTitle(view.getDefaultTitle());
        newStage.initStyle(view.getDefaultStyle());
        newStage.showAndWait();
    }


    private void enableTray(Stage stage) {

        try {

            SystemTray tray = SystemTray.getSystemTray();
            oldtrayIcon = new SystemTrayIcon(ImageIO.read(AbstractJavaFxApplicationSupport.class.getResource("/assets/opc.png").openStream()), "g", popupMenu);
            oldtrayIcon.setToolTip("GNC机床通讯服务端");

            tray.add(oldtrayIcon);
            /*版权信息*/
            readLicense();
            /*设置*/
            doSetting();
            /*显示*/
            showing(stage);
            /*退出*/
            exiting();


//            //添加鼠标点击事件
            oldtrayIcon.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    //单机左键
                    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
                        Platform.runLater(() -> {
                            if (stage.isIconified()) {
                                stage.setIconified(false);
                            }
                            if (!stage.isShowing()) {
                                stage.show();
                            }
                            stage.toFront();
                        });
                    }
                    //鼠标右键,关闭应用
                    // else if (e.getButton() == MouseEvent.BUTTON3) {
                    // Platform.setImplicitExit(true);
                    //tray.remove(trayIcon);
                    // Platform.runLater(stage::close);
                    //
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*版权*/
    static void readLicense() throws IOException {
        JMenuItem settingMenu = new JMenuItem("版权信息");
        settingMenu.setPreferredSize(new Dimension(fontSize * 2, fontSize * 2));
        settingMenu.setIcon(new ImageIcon(Objects.requireNonNull(ImageUtil.resizeTrayPng(AbstractJavaFxApplicationSupport.class.getResource("/assets/license.png").openStream(), iconSize, iconSize))));

        settingMenu.setFont(derivedFont);
        settingMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            new Alert(AlertType.INFORMATION, "大连光洋@桑杰逊开发 业务合作邮箱:1009900122@qq.com").showAndWait();
                        }
                    });
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        popupMenu.add(settingMenu);
    }


    /*设置*/
    static void doSetting() throws IOException {
        JMenuItem settingMenu = new JMenuItem("设置");
//        settingMenu.setPreferredSize(new Dimension(fontSize * 2, fontSize * 2));
        settingMenu.setIcon(new ImageIcon(Objects.requireNonNull(ImageUtil.resizeTrayPng(AbstractJavaFxApplicationSupport.class.getResource("/assets/setting.png").openStream(), iconSize, iconSize))));

        settingMenu.setFont(derivedFont);
        settingMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    Platform.runLater(new Runnable() {
                        @SneakyThrows
                        @Override
                        public void run() {
                            new Alert(Alert.AlertType.NONE, "预留", new ButtonType[]{ButtonType.CLOSE}).show();
                        }
                    });
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        popupMenu.add(settingMenu);
    }

    /*退出*/
    @SneakyThrows
    private static void exiting() {

        //退出菜单
        JMenuItem exitMenu = new JMenuItem("退出");
        exitMenu.setPreferredSize(new Dimension(fontSize * 12, fontSize * 2));
        exitMenu.setIcon(new ImageIcon(Objects.requireNonNull(ImageUtil.resizeTrayPng(AbstractJavaFxApplicationSupport.class.getResource("/assets/exit.png").openStream(), iconSize, iconSize))));
        exitMenu.setFont(derivedFont);
        exitMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            System.gc();
                            Platform.exit();
                            System.exit(0);
                        }
                    });
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        popupMenu.add(exitMenu);
    }

    /*显示*/
    @SneakyThrows
    private static void showing(Stage stage) {
        JMenuItem showMenu = new JMenuItem("显示");
        showMenu.setPreferredSize(new Dimension(fontSize * 8, fontSize * 2));
        //Paths.get("./src/main/resources/assets/exit.png").toUri().toURL()
        //getClass().getResource("/assets/settings.png").toExternalForm()
        showMenu.setIcon(new ImageIcon(Objects.requireNonNull(ImageUtil.resizeTrayPng(AbstractJavaFxApplicationSupport.class.getResource("/assets/showing.png").openStream(), iconSize, iconSize))));
        showMenu.setFont(derivedFont);
        showMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            if (!stage.isShowing()) {
                                stage.show();
                            }
                            stage.toFront();
                        }
                    });
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        popupMenu.add(showMenu);
    }


    private void loadIcons(ConfigurableApplicationContext ctx) {
        try {
            final List<String> fsImages = PropertyReaderHelper.get(ctx.getEnvironment(), Constant.KEY_APPICONS);

            if (!fsImages.isEmpty()) {
                fsImages.forEach((s) ->
                        {
                            Image img = new Image(getClass().getResource(s).toExternalForm());
                            icons.add(img);
                        }
                );
            } else { // add factory images
                icons.addAll(defaultIcons);
            }
        } catch (Exception e) {
            log.error("Failed to load icons: ", e);
        }

    }


    /*
     * (non-Javadoc)
     *
     * @see javafx.application.Application#init()
     */
    @Override
    public void init() throws Exception {
//        SpringApplication springapplication = new SpringApplication(ApplicationController.class);
        // Load in JavaFx Thread and reused by Completable Future, but should no be a big deal.
        defaultIcons.addAll(loadDefaultIcons());
        CompletableFuture.supplyAsync(
                () ->
                        SpringApplication.run(this.getClass(), savedArgs)

        ).whenComplete((ctx, throwable) -> {
            if (throwable != null) {
                log.error("Failed to load spring application context: ", throwable);
                Platform.runLater(() -> showErrorAlert(throwable));
            } else {
                Platform.runLater(() -> {
                    loadIcons(ctx);
                    launchApplicationView(ctx);
                });
            }
        }).thenAcceptBothAsync(splashIsShowing, (ctx, closeSplash) -> {
            Platform.runLater(closeSplash);
        });
    }


    /*
     * (non-Javadoc)
     *
     * @see javafx.application.Application#start(javafx.stage.Stage)
     */
    @Override
    public void start(final Stage stage) throws Exception {

        GUIState.setStage(stage);
//        GUIState.setHostServices(this.getHostServices());
        final Stage splashStage = new Stage(StageStyle.TRANSPARENT);

        if (AbstractJavaFxApplicationSupport.splashScreen.visible()) {
            final Scene splashScene = new Scene(splashScreen.getParent(), Color.TRANSPARENT);
            splashStage.setScene(splashScene);
            splashStage.getIcons().addAll(defaultIcons);
            splashStage.initStyle(StageStyle.TRANSPARENT);
//            stage.setTitle("光洋唯斯");
            splashStage.setTitle("GNC机床通讯服务端");
            beforeShowingSplash(splashStage);
            splashStage.show();
        }

        splashIsShowing.complete(() -> {
            showInitialView();
            if (AbstractJavaFxApplicationSupport.splashScreen.visible()) {
                splashStage.hide();
                splashStage.setScene(null);
            }

        });
    }


    /**
     * Show initial view.
     */
    private void showInitialView() {
        final String stageStyle = applicationContext.getEnvironment().getProperty(Constant.KEY_STAGE_STYLE);
        if (stageStyle != null) {
            GUIState.getStage().initStyle(StageStyle.valueOf(stageStyle.toUpperCase()));
        } else {
            GUIState.getStage().initStyle(StageStyle.DECORATED);
        }

        beforeInitialView(GUIState.getStage(), applicationContext);

        showView(savedInitialView);
    }


    /**
     * Launch application view.
     */
    private void launchApplicationView(final ConfigurableApplicationContext ctx) {
        AbstractJavaFxApplicationSupport.applicationContext = ctx;
    }

    /**
     * Show view.
     *
     * @param newView the new view
     */
    public void showView(final Class<? extends AbstractFxmlView> newView) {
        try {
            final AbstractFxmlView view = applicationContext.getBean(newView);

            if (GUIState.getScene() == null) {
                GUIState.setScene(new Scene(view.getView()));
            } else {
                GUIState.getScene().setRoot(view.getView());


            }
            GUIState.getStage().setScene(GUIState.getScene());
            applyEnvPropsToView();
            enableTray(GUIState.getStage());

            GUIState.getStage().getIcons().addAll(icons);

//            //grab your root here
//            GUIState.getScene().setOnMousePressed(event -> {
//                xOffset = event.getSceneX();
//                yOffset = event.getSceneY();
//            });
//
//            //move around here
//            GUIState.getScene().setOnMouseDragged(event -> {
//                GUIState.getStage().setX(event.getScreenX() - xOffset);
//                GUIState.getStage().setY(event.getScreenY() - yOffset);
//            });


            GUIState.getStage().hide();

        } catch (Throwable t) {
            log.error("Failed to load application: ", t);
//            showErrorAlert(t);
        }
    }

    /**
     * Show error alert that close app.
     *
     * @param throwable cause of error
     */
    @SneakyThrows
    private static void showErrorAlert(Throwable throwable) {
        Alert alert = new Alert(AlertType.ERROR, "请检查错误!\n" +

                "该软件即将关闭.\n\n" +
                "Error: " + throwable.getMessage());
        alert.showAndWait().ifPresent(response -> Platform.exit());
    }

    /**
     * Apply env props to view.
     */
    private static void applyEnvPropsToView() {
        PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_TITLE, String.class,
                GUIState.getStage()::setTitle);

        PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_WIDTH, Double.class,
                GUIState.getStage()::setWidth);

        PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_HEIGHT, Double.class,
                GUIState.getStage()::setHeight);

        PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), Constant.KEY_STAGE_RESIZABLE, Boolean.class,
                GUIState.getStage()::setResizable);
    }

    /*
     * (non-Javadoc)
     *
     * @see javafx.application.Application#stop()
     */
    @Override
    public void stop() throws Exception {
        super.stop();
        if (applicationContext != null) {
            applicationContext.close();
        } // else: someone did it already
    }

    /**
     * Sets the title. Allows to overwrite values applied during construction at
     * a later time.
     *
     * @param title the new title
     */
    protected static void setTitle(final String title) {
        GUIState.getStage().setTitle(title);
    }

    /**
     * Launch app.
     *
     * @param appClass the app class
     * @param view     the view
     * @param args     the args
     * @return
     */
    public static Class<?> launch(final Class<? extends Application> appClass,
                                  final Class<? extends AbstractFxmlView> view, final String[] args) {

        launch(appClass, view, new SplashScreen(), args);
        return null;
    }

    /**
     * Launch app.
     *
     * @param appClass the app class
     * @param view     the view
     * @param args     the args
     * @deprecated To be more in line with javafx.application please use launch
     */
    @Deprecated
    public static void launchApp(final Class<? extends Application> appClass,
                                 final Class<? extends AbstractFxmlView> view, final String[] args) {

        launch(appClass, view, new SplashScreen(), args);
    }

    /**
     * Launch app.
     *
     * @param appClass     the app class
     * @param view         the view
     * @param splashScreen the splash screen
     * @param args         the args
     */
    public static void launch(final Class<? extends Application> appClass,
                              final Class<? extends AbstractFxmlView> view, final SplashScreen splashScreen, final String[] args) {
        savedInitialView = view;
        savedArgs = args;

        if (splashScreen != null) {
            AbstractJavaFxApplicationSupport.splashScreen = splashScreen;
        } else {
            AbstractJavaFxApplicationSupport.splashScreen = new SplashScreen();
        }

        if (SystemTray.isSupported()) {
            GUIState.setSystemTray(SystemTray.getSystemTray());
        }

        Application.launch(appClass, args);
    }

    /**
     * Launch app.
     *
     * @param appClass     the app class
     * @param view         the view
     * @param splashScreen the splash screen
     * @param args         the args
     * @deprecated To be more in line with javafx.application please use launch
     */
    @Deprecated
    public static void launchApp(final Class<? extends Application> appClass,
                                 final Class<? extends AbstractFxmlView> view, final SplashScreen splashScreen, final String[] args) {
        launch(appClass, view, splashScreen, args);
    }

    /**
     * Gets called after full initialization of Spring application context
     * and JavaFX platform right before the initial view is shown.
     * Override this method as a hook to add special code for your app. Especially meant to
     * add AWT code to add a system tray icon and behavior by calling
     * GUIState.getSystemTray() and modifying it accordingly.
     * <p>
     * By default noop.
     *
     * @param stage can be used to customize the stage before being displayed
     * @param ctx   represents spring ctx where you can loog for beans.
     */
    public void beforeInitialView(final Stage stage, final ConfigurableApplicationContext ctx) {
    }

    public void beforeShowingSplash(Stage splashStage) {

    }

    public Collection<Image> loadDefaultIcons() {
//        return Arrays.asList(new Image(getClass().getResource("/icons/wise_16X16.png").toExternalForm()),
////                new Image(getClass().getResource("/icons/wise_24x24.png").toExternalForm()),
//                new Image(getClass().getResource("/icons/wise_32X32.png").toExternalForm()),
////                new Image(getClass().getResource("/icons/wise_42x42.png").toExternalForm()),
//                new Image(getClass().getResource("/icons/wise_64X64.png").toExternalForm()));


        return Arrays.asList(new Image(getClass().getResource("/icons/gona_16X16.png").toExternalForm()),
//                new Image(getClass().getResource("/icons/wise_24x24.png").toExternalForm()),
                new Image(getClass().getResource("/icons/gona_32X32.png").toExternalForm()),
//                new Image(getClass().getResource("/icons/wise_42x42.png").toExternalForm()),
                new Image(getClass().getResource("/icons/gona_64X64.png").toExternalForm()));
    }


}
